package com.cloud.api.annotation;

import cn.hutool.core.lang.UUID;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.cloud.core.exception.MyException;
import jakarta.annotation.Resource;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Aspect
@Configuration
public class LocalLockInterceptor {


    @Resource(name = "myRedis")
    private RedisTemplate<String, Object> redisTemplate;

    @Around("execution(public * *(..)) && @annotation(com.cloud.api.annotation.LocalLock)")
    public Object interceptor(ProceedingJoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        LocalLock localLock = method.getAnnotation(LocalLock.class);
        String value = getAnnotationValue(joinPoint, localLock.value());


        String key = "localLock_" + method.getName() + "_" +localLock.key() + "_" + value;
        String uuid = UUID.fastUUID().toString();

        long startTime = System.currentTimeMillis();
        while (true){
            boolean lock = getLock(key, uuid, localLock.expire());
            if (lock){
                break;
            }
            // 检查是否超时
            long currentTime = System.currentTimeMillis();
            if (localLock.acquireTimeout() != -1L && currentTime - startTime >= localLock.acquireTimeout()) {
                throw new MyException("exception.error.timeout");
            }
        }
        try{
            return joinPoint.proceed();
        } catch (Throwable throwable) {
            throw new RuntimeException(throwable.getMessage(),throwable);
        } finally {
            releaseLock(key, uuid, localLock.expire());
        }

    }

    // 简单加锁
    public boolean getLock(String key, String requestId, Long expireTime) {
//        expireTime的单位是毫秒
        List<String> list = Arrays.asList(key, requestId,String.valueOf(expireTime));
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/lock.lua")));
        // 指定返回类型
        redisScript.setResultType(Long.class);
        Long result = redisTemplate.execute(redisScript, list);
        // 返回1:代表加锁成功，返回-1:代表加锁失败
        if (result == 1L) {
            return true;
        }
        return false;
    }

    // 简单解锁
    public boolean releaseLock(String key, String requestId, Long expireTime) {
        List<String> list = Arrays.asList(key, requestId,String.valueOf(expireTime));
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/unlock.lua")));
        // 指定返回类型
        redisScript.setResultType(Long.class);
        Long result = redisTemplate.execute(redisScript,list);
        //1是全部解锁，0是部分解锁，-1是解锁失败
        if (result == 1L) {
            return true;
        }
        return false;
    }

    /**
     * 获取注解中传递的动态参数的参数值
     *
     * @param joinPoint
     * @param name
     * @return
     */
    public String getAnnotationValue(JoinPoint joinPoint, String name) {
        String paramName = name;
        // 获取方法中所有的参数
        Map<String, Object> params = getParams(joinPoint);
        // 参数是否是动态的:#{paramName}
        if (paramName.matches("^#\\{\\D*\\}")) {
            // 获取参数名
            paramName = paramName.replace("#{", "").replace("}", "");
            // 是否是复杂的参数类型:对象.参数名
            if (paramName.contains(".")) {
                String[] split = paramName.split("\\.");
                // 获取方法中对象的内容
                Object object = getValue(params, split[0]);
                // 转换为JsonObject
                JSONObject jsonObject = JSONUtil.parseObj(object);
                // 获取值
                Object o = jsonObject.get(split[1]);
                return String.valueOf(o);
            }
            // 简单的动态参数直接返回
            return String.valueOf(getValue(params, paramName));
        }
        // 非动态参数直接返回
        return name;
    }



    /**
     * 根据参数名返回对应的值
     *
     * @param map
     * @param paramName
     * @return
     */
    public Object getValue(Map<String, Object> map, String paramName) {
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            if (entry.getKey().equals(paramName)) {
                return entry.getValue();
            }
        }
        return null;
    }

    /**
     * 获取方法的参数名和值
     *
     * @param joinPoint
     * @return
     */
    public Map<String, Object> getParams(JoinPoint joinPoint) {
        Map<String, Object> params = new HashMap<>(8);
        Object[] args = joinPoint.getArgs();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String[] names = signature.getParameterNames();
        for (int i = 0; i < args.length; i++) {
            params.put(names[i], args[i]);
        }
        return params;
    }

}
